﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace Hellquest
{
    enum CollissionBounds
    {
        Box,
        Sphere,
        ShapeApproximation
    }

    public class CollisionInfo
    {
        public Collider collider1;
        public Collider collider2;
        public Vector2 projection;

        public CollisionInfo (Collider _coll1, Collider _coll2, Vector2 _projection)
	    {
            collider1 = _coll1;
            collider2 = _coll2;
            projection = _projection;
	    }

        public override bool Equals(object obj)
        {
            if (obj is CollisionInfo)
                return ((obj as CollisionInfo).collider1.Equals(collider1) && (obj as CollisionInfo).collider2.Equals(collider2)) ||
                       ((obj as CollisionInfo).collider2.Equals(collider1) && (obj as CollisionInfo).collider1.Equals(collider2));
            return base.Equals(obj);
        }
    }

    public class PhysicsEngine : GameComponent
    {
        public Vector2 Gravity = new Vector2(0, /*100*/0);

        public bool IsActive = false;

        public static PhysicsEngine Instance { get { return mInstance; } }
        private static PhysicsEngine mInstance;

        private List<Rigidbody> rigidbodies;
        private List<ForceField> forceFields;
        private List<Collider> colliders;
        private Dictionary<Collider, Collider> collisionPairs;
        private Dictionary<Collider, Collider> resolvedCollisionPairs;
        private List<CollisionInfo> collisions;

        private GameObject mouseForce;

        public PhysicsEngine(Game _game)
            : base(_game)
        {
            mInstance = this;
            rigidbodies = new List<Rigidbody>();
            forceFields = new List<ForceField>();
            colliders = new List<Collider>();
            collisionPairs = new Dictionary<Collider, Collider>();
            resolvedCollisionPairs = new Dictionary<Collider, Collider>();
            collisions = new List<CollisionInfo>();

            mouseForce = GameObject.Initialize("MouseForce", Vector2.Zero,
                                               new Dictionary<Type, object[]>()
                                               {
                                                   {typeof(ForceField), new object[]{ForceFieldType.Vortex, 800, 800, 100f}}
                                               });
        }

        public void Reset()
        {
            mouseForce = GameObject.Initialize("MouseForce", Vector2.Zero,
                                               new Dictionary<Type, object[]>()
                                               {
                                                   {typeof(ForceField), new object[]{ForceFieldType.Vortex, 800, 800, 100f}}
                                               });
        }
        
        public void AddForceField(ForceField _field)
        {
            forceFields.Add(_field);
        }

        public void RemoveForceField(ForceField _field)
        {
            forceFields.Remove(_field);
        }

        public void AddRigidbody(Rigidbody _body)
        {
            rigidbodies.Add(_body);
        }

        public void RemoveRigidbody(Rigidbody _body)
        {
            rigidbodies.Remove(_body);
        }

        public void AddCollider(Collider _collider)
        {
            colliders.Add(_collider);
        }

        public void RemoveCollider(Collider _collider)
        {
            colliders.Remove(_collider);
        }

        public override void Update(GameTime gameTime)
        {
            float timeDiff = (float)gameTime.ElapsedGameTime.TotalSeconds;

            StartFrame();

            Integrate(timeDiff);
            collisions = CollisionCheck();

            foreach (CollisionInfo collision in collisions)
            {
                if (collision.collider1.gameObject.rigidbody != null && collision.collider2.gameObject.rigidbody != null)
                {
                    Console.WriteLine(collision.projection);
                    //Move apart
                    collision.collider1.gameObject.rigidbody.newPosition += collision.projection;
                    collision.collider2.gameObject.rigidbody.newPosition -= collision.projection;

                    collision.projection.Normalize();
                    //Change Velocity
                    collision.collider1.gameObject.rigidbody.newVelocity = Vector2.Reflect(collision.collider1.gameObject.rigidbody.newVelocity, collision.projection);
                    collision.collider2.gameObject.rigidbody.newVelocity = Vector2.Reflect(collision.collider2.gameObject.rigidbody.newVelocity, collision.projection);
                }
                else 
                {
                    if (collision.collider2.gameObject.rigidbody != null)
                    {
                        collision.collider2.gameObject.rigidbody.newPosition += collision.projection;
                        collision.projection.Normalize();
                        collision.collider2.gameObject.rigidbody.newVelocity = Vector2.Reflect(collision.collider2.gameObject.rigidbody.newVelocity, collision.projection);
                    }
                    else if (collision.collider1.gameObject.rigidbody != null)
                    {
                        collision.collider1.gameObject.rigidbody.newPosition += collision.projection;
                        collision.projection.Normalize();
                        collision.collider1.gameObject.rigidbody.newVelocity = Vector2.Reflect(collision.collider1.gameObject.rigidbody.newVelocity, collision.projection);
                    }
                }
            }

            foreach (Rigidbody body in rigidbodies)
                body.EndFrame();

            base.Update(gameTime);
        }

        private void StartFrame()
        {
            resolvedCollisionPairs.Clear();
            collisionPairs.Clear();
            foreach (Rigidbody body in rigidbodies)
            {
                if (body.IsActive)
                    body.ApplyForce(Gravity);
                body.StartIntegration(); 
            }
        }

        private void Integrate(float _timeDiff)
        {
            foreach (Rigidbody body in rigidbodies)
            {
                body.IntegrateTimestep(_timeDiff);
            }
        }

        private List<CollisionInfo> CollisionCheck()
        {
            List<CollisionInfo> returnColls = new List<CollisionInfo>();
            for (int i = 0; i < colliders.Count-1; i++)
            {
                Collider coll1 = colliders[i];
                for(int j = i+1; j < colliders.Count; j++)
                {
                    Collider coll2 = colliders[j];
                    Vector2 projection = Vector2.Zero;
                    if(coll1.Intersects(coll2, ref projection))
                    {
                        CollisionInfo collInfo = new CollisionInfo(coll1, coll2, projection);
                        if (!returnColls.Contains(collInfo))
                        {
                            returnColls.Add(collInfo);
                        }
                    }
                }
            }
            return returnColls;
        }

        private void CollisionResponse()
        {

        }
    }
}
